---
title: MDX Guide
description: Setting up MDX for web
---

In building out this beautiful website, and the equally beautiful [tamagui.dev](https://tamagui.dev), the surprising
most difficult part was getting MDX to work well.

Since many blogs, apps, and documentation sites want to display Markdown content, we figured a guide showing how
we did it will be useful.

We think long-term someone should write a much better mdx package, as ours is simply pieced together from a bunch of packages and code we ported long ago to Next.js, but nonetheless it works. It uses a hodge-podge of [Rehype](https://github.com/rehypejs/rehype) plugins to do the trick, alongside [mdx-bundler](https://github.com/kentcdodds/mdx-bundler).

### Setting up your data dir

The first step is to create a directory for your mdx files to live:

<RouteTree
  routes={[
    { name: 'data', children: [
      { name: 'hello-world.mdx', description: 'Your MDX content goes here' },
    ] },
  ]}
/>

Throw some stuff in that file:

```
---
title: MDX Guide
description: Setting up MDX for web
---

In building out this beautiful website, and the equally beautiful [tamagui.dev](https://tamagui.dev), the surprising
most difficult part was getting MDX to work well...
```

### Setting up `@vxrn/mdx`

Next, we add our mdx dependencies:

```bash
yarn add @vxrn/mdx mdx-bundler
```

We set up our MDX components (using Tamagui in this example):

```tsx fileName=features/MDXComponents.tsx
import { Text } from "react-native";

export const components = {
  h1: ({ children }) => (
    <Text style={{ fontSize: 24, fontWeight: "bold", marginBottom: 10 }}>
      {children}
    </Text>
  ),
  h2: ({ children }) => (
    <Text style={{ fontSize: 20, fontWeight: "bold", marginBottom: 8 }}>
      {children}
    </Text>
  ),
  h3: ({ children }) => (
    <Text style={{ fontSize: 18, fontWeight: "bold", marginBottom: 6 }}>
      {children}
    </Text>
  ),
  p: ({ children }) => <Text style={{ marginBottom: 10 }}>{children}</Text>,
};

```

And then all we need is a new route:

```tsx fileName=app/docs/[slug].tsx
import { getMDXComponent } from "mdx-bundler/client";
import { useMemo } from "react";
import { useLoader } from "one";
import { Text, View } from "react-native";
import { components } from "~/features/MDXComponents";

export async function generateStaticParams() {
  const { getAllFrontmatter } = await import("@vxrn/mdx");
  const frontmatters = getAllFrontmatter("data");
  const paths = frontmatters.map(({ slug }) => ({
    slug: slug.replace(/.*docs\//, ""),
  }));
  return paths;
}

export async function loader({ params }) {
  const { getMDXBySlug } = await import("@vxrn/mdx");
  const { frontmatter, code } = await getMDXBySlug("data", params.slug);
  return {
    frontmatter,
    code,
  };
}

export function DocsPage() {
  const { code, frontmatter } = useLoader(loader);
  const Component = useMemo(() => getMDXComponent(code), [code]);

  return (
    <View style={{ padding: 20 }}>
      <Text style={{ fontSize: 48, fontWeight: "bold", marginBottom: 10 }}>
        {frontmatter.title}
      </Text>
      <Component components={components} />
    </View>
  );
}

```

Note that we `await import` the `@vxrn/mdx` library - this is because One by default builds out your server
and API routes to CommonJS, but some of the rehype/remak dependencies are ESM. Long story short - this makes it work.

### Configuring Vite for MDX Support

To ensure that MDX works correctly with your One project, you'll need to configure Vite. Update your `vite.config.ts` file with the following content:

```ts fileName=vite.config.ts
import { defineConfig } from 'vite'
import { one } from 'one/vite'

export default defineConfig({
  ssr: {
    noExternal: true,
    external: ['@vxrn/mdx'],
  },
  plugins: [
    one({
      web: {
        defaultRenderMode: 'ssg',
      },
    }),
  ],
})
```

This configuration does a few important things:

1. It sets `noExternal: true` in the SSR options, which tells Vite to bundle all dependencies for server-side rendering.
2. It explicitly marks `@vxrn/mdx` as an external dependency, which is necessary because it contains ESM modules that shouldn't be bundled.



---

That *should* do it. We say should, because there's a lot going on here, and many sub-deps inside `@vxrn/mdx` that need to work together. Let us know if this works for you!

